package com.beiding.render;

import jdk.nashorn.api.scripting.ScriptObjectMirror;

import java.io.*;
import java.util.*;

/*
    该引擎仅供参考,没有实质意义
 */
//根目录是可变的
public class FileTemplateEngine implements TemplateFinder, HandlerFinder, PluginFinder, ResourceFinder {


    //模板文件根目录
    protected File templateRoot;

    //处理器文件根目录
    protected File handlerRoot;

    //插件根目录
    protected File pluginRoot;

    //资源根路径
    protected File resourceRoot;

    protected File root;


    protected static class CoordinateData {
        public String target;
        public String targetVersion;
        public String targetKey;

        public String from;
        public String fromKey;
        public String fromVersion;

    }

    public File getRoot() {
        return root;
    }

    public File getHandlerRoot() {
        return handlerRoot;
    }

    public File getTemplateRoot() {
        return templateRoot;
    }


    //
    protected static CoordinateData parseCoordinate(String relative, String path) {

        relative = relative.trim();

        //分离版本信息
        int index = relative.indexOf(':');
        String version = null;
        if (index != -1) {
            version = relative.substring(0, index);
            relative = relative.substring(index + 1);
        }


        CoordinateData coordinateResult = new CoordinateData();
        coordinateResult.from = relative;
        coordinateResult.fromVersion = version;

        //放入来源key
        String key = relative;
        if (version != null) {
            key = version + ":" + key;
        }


        coordinateResult.fromKey = key;

        //计算相对路径
        path = path.trim();
        index = path.indexOf(':');
        version = null;
        if (index != -1) {
            version = path.substring(0, index);
            path = path.substring(index + 1);
        }

        String absolute = CoordinateUtils.relativeToAbsolute(relative, path);

        if (absolute.endsWith("/")) {
            throw new RuntimeException("非法坐标:" + absolute);
        } else if (absolute.equals("")) {
            throw new RuntimeException("未给定坐标");
        }


        key = absolute;
        if (version != null) {
            key = version + ":" + key;
        }

        coordinateResult.target = absolute;
        coordinateResult.targetVersion = version;
        coordinateResult.targetKey = key;

        return coordinateResult;
    }

    public File getPluginRoot() {
        return pluginRoot;
    }

    public File getResourceRoot() {
        return resourceRoot;
    }

    //模板编译期
    private TemplateCompiler templateCompiler;

    public TemplateCompiler getTemplateCompiler() {
        return templateCompiler;
    }

    //文件输出
    public static BoxManager createBoxManager(File root) {

        if (!root.exists()) {
            root.mkdirs();
        } else if (root.isFile()) {
            throw new RuntimeException("期望文件夹" + root.getAbsolutePath());
        }

        return new BoxManager() {
            @Override
            public Box create(String expression) {
                File file = new File(root, expression);

                return new Box() {
                    @Override
                    public void write(String content) {
                        try {
                            if (!file.getParentFile().exists()) {
                                file.getParentFile().mkdirs();
                            } else {
                                if (file.getParentFile().isFile()) {
                                    throw new RuntimeException("期望文件夹" + file.getParentFile().getAbsolutePath());
                                }
                            }
                            FileWriter fileWriter = new FileWriter(file);
                            fileWriter.write(content);
                            fileWriter.close();
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }

                    @Override
                    public boolean exist() {
                        return file.exists();
                    }


                    @Override
                    public boolean canWrite() {
                        return !file.exists() || file.isFile();
                    }

                    @Override
                    public boolean delete() {
                        return file.delete();
                    }

                    @Override
                    public String read() {
                        return null;
                    }
                };
            }

            @Override
            public boolean exist(String expression) {
                return false;
            }
        };

    }

    public FileTemplateEngine(File root) {
        this();
        setRoot(root);
    }

    private List<RenderFilter> filters = new ArrayList<RenderFilter>() {
        {
            add(new RenderFilter() {
                @Override
                public void filter(This _this, NextFilterHolder nextFilterHolder) {
                    _this.render();
                }

                @Override
                public int order() {
                    return Integer.MAX_VALUE;
                }
            });
        }
    };

    public void addFilter(RenderFilter filter) {
        filters.add(filter);
        filters.sort(Comparator.comparingInt(RenderFilter::order));
    }

    public void remove(RenderFilter filter) {
        filters.remove(filter);
    }

    public FileTemplateEngine() {
        //缺省的根目录放在用户目录下的 .TextRender目录下
        File userHome = new File(System.getProperty("user.home"));
        File root = new File(userHome, "/.TextRender");
        setRoot(root);
        templateCompiler = new TemplateCompiler();

        RenderTime renderTime = new RenderTime(this, this, this, this, filters);
        templateCompiler.setRenderTime(renderTime);

    }

    public void setRoot(File root) {
        if (root.isFile()) {
            throw new RuntimeException("期望文件夹:" + root);
        }
        this.root = root;
        this.templateRoot = new File(root, "template");
        this.handlerRoot = new File(root, "handler");
        this.pluginRoot = new File(root, "plugin");
        this.resourceRoot = new File(root, "name");
        if (this.pluginRoot.isFile()) {
            throw new RuntimeException("期望文件夹:" + pluginRoot);
        }
        if (this.templateRoot.isFile()) {
            throw new RuntimeException("期望文件夹:" + templateRoot);
        }
        if (this.handlerRoot.isFile()) {
            throw new RuntimeException("期望文件夹:" + handlerRoot);
        }
        if (this.resourceRoot.isFile()) {
            throw new RuntimeException("期望文件夹:" + resourceRoot);
        }

    }


    private Map<String, Template> templateCache = new HashMap<>();


    //使用文本定义模板
    public Template defineTemplate(String coordinate, String content) {
        return this.templateCompiler.compile(content, coordinate);
    }


    public Template doFindTemplate(CoordinateData coordinateData) throws Exception {

        //默认的处理机制忽略版本信息
        try {
            FileInputStream inputStream = new FileInputStream(new File(templateRoot, coordinateData.target + ".xml"));
            String s = FileUtils.readInputStreamAsString(inputStream);
            Template template = defineTemplate(coordinateData.targetKey, s);
            inputStream.close();
            return template;
        } catch (Exception ignore) {
            return null;
        }
    }

    //缓存
    @Override
    public Template findTemplate(String relative, String path) {

        try {

            CoordinateData coordinate = parseCoordinate(relative, path);
            Template template = templateCache.get(coordinate.targetKey);
            if (template != null) {
                return template;
            }
            template = doFindTemplate(coordinate);
            if (template != null) {
                templateCache.put(coordinate.targetKey, template);
            }
            return template;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

    public Resource doFindResource(CoordinateData coordinateData) {

        //TODO 静态资源不会处理文件后缀
        File file = new File(resourceRoot, coordinateData.target);

        if (!file.exists()) {
            return null;
        }

        return new Resource() {
            /*    @Override
                public String getName() {
                    return file.getName();
                }
    *//*
            @Override
            public String getCoordinate() {
                return coordinateData.target;
            }
*/
            @Override
            public byte[] load() throws IOException {
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                FileInputStream fileInputStream = new FileInputStream(file);
                FileUtils.copy(fileInputStream, byteArrayOutputStream);
                fileInputStream.close();
                return byteArrayOutputStream.toByteArray();
            }

            @Override
            public boolean isEffective() {
                return file.exists();
            }
        };

    }

    //不需要资源缓存
    @Override
    public Resource findResource(String relative, String path) {
        CoordinateData coordinate = parseCoordinate(relative, path);
        return doFindResource(coordinate);
    }

    private Map<String, Handler> handlerCache = new HashMap<>();

    private Map<String, Object> isolationMap;

    {
        isolationMap = new HashMap<>();
        isolationMap.put("handlers", new HandlerGetter());
    }

    public class HandlerGetter {
        public Handler get(String path) {
            return findHandler(path);
        }

        public Handler get(String relative, String path) {
            return findHandler(relative, path);
        }
    }


    protected Handler defineHandler(String content) throws Exception {//这里的脚本引擎

        Object exe = ScriptUtils.exe(new HashMap(isolationMap), content + "\n\nhandler");

        if (exe == null) {
            throw new RuntimeException("定义失败:" + content);
        }

        ScriptObjectMirror callAble = (ScriptObjectMirror) exe;

        //定义处理器的名称
        String handlerName = "handler_" + ContentUtils.createId();

        //创建为handler
        return new Handler() {
            @Override
            public Object handle(Object... params) {//将数据带回来
                try {

                    return callAble.call(This.current(), params);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public String name() {
                return handlerName;
            }

        };

    }

    public Handler doFindHandler(CoordinateData coordinateData) throws Exception {
        String js;
        try (FileInputStream inputStream = new FileInputStream(new File(handlerRoot, coordinateData.target + ".js"))) {
            js = FileUtils.readInputStreamAsString(inputStream);
        }
        return defineHandler(js);
    }

    //绝对坐标
    @Override
    public Handler findHandler(String relative, String path) {

        try {

            CoordinateData coordinateResult = parseCoordinate(relative, path);

            //首先判断是否具有缓存
            Handler handler = handlerCache.get(coordinateResult.targetKey);
            if (handler != null) {
                return handler;
            }

            handler = doFindHandler(coordinateResult);

            if (handler != null) {
                handlerCache.put(coordinateResult.targetKey, handler);
            }


            return handler;
        } catch (Exception e) {
            return null;
        }

    }

    private Map<String, Map<String, Object>> pluginCache = new HashMap<>();

    @Override
    public Map<String, Object> findPluginBeans(String relative, String plugins) {

        String[] split = plugins.split(",");
        Map<String, Object> r = new HashMap<>();
        for (String path : split) {
            path = path.trim();
            if (!path.equals("")) {
                CoordinateData coordinateResult = parseCoordinate(relative, path);

                //尝试从本地文件中加载
                Map<String, Object> map = scanPluginBeans(coordinateResult);
                if (map != null) {
                    for (Map.Entry<String, Object> entry : map.entrySet()) {
                        if (r.containsKey(entry.getKey())) {
                            Object o = r.get(entry.getKey());
                            System.err.println(entry.getKey() + " 的类型 " + o.getClass().getName() + " " + " 将被替换为 " + entry.getValue().getClass().getName());
                        }
                        r.put(entry.getKey(), entry.getValue());
                    }
                }

            }
        }

        return r;
    }


    public Map<String, Object> scanPluginBeans(File file) {

        if (file.exists()) {
            Map<String, Object> map = new HashMap<>();
            try {

                Set<Class> scan = ClassUtils.scan(file);
                if (scan.size() > 0) {
                    for (Class aClass : scan) {
                        if (aClass.getSimpleName().startsWith("BEAN_")) {
                            String name = aClass.getSimpleName().substring("BEAN_".length());
                            try {
                                Object newInstance = aClass.newInstance();
                                map.put(name, newInstance);
                            } catch (Exception e) {
                                System.err.println(aClass.getName() + "初始化失败");
                            }
                        }
                    }
                }
            } catch (Exception ignore) {

            }

            return map;
        }

        return null;

    }


    public Map<String, Object> doScanPluginBeans(CoordinateData coordinateData) throws Exception {

        //版本号忽略

        File jar = new File(pluginRoot, coordinateData.target + ".jar");
        return scanPluginBeans(jar);
    }


    private Map<String, Object> scanPluginBeans(CoordinateData coordinateData) {


        Map<String, Object> map = pluginCache.get(coordinateData.targetKey);
        if (map != null) {
            return map;
        }

        try {
            map = doScanPluginBeans(coordinateData);
            if (map != null) {
                pluginCache.put(coordinateData.targetKey, map);
            }
            return map;

        } catch (Exception ignore) {
            return Collections.emptyMap();
        }

    }


    //清空缓存
    public void clearCache(String ant) {
        if (ant == null) {
            return;
        }

        String s = ContentUtils.antToRegex(ant);
        handlerCache.keySet().removeIf(next -> next.matches(s));
        pluginCache.keySet().removeIf(next -> next.matches(s));
        templateCache.keySet().removeIf(next -> next.matches(s));
    }

    public void clearCache() {

        handlerCache.clear();
        pluginCache.clear();
        templateCache.clear();
    }
}
